﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

using LeanEngine.Entity;
using LeanEngine.Collections;
using LeanEngine.Data.Linq.Language;

namespace LeanEngine.Data.Linq
{
    /// <summary>
    /// Provides extension methods for System.Linq.IQueryable paging data.
    /// </summary>
    public static class PagingExtensions
    {
        private struct PagingParameter
        {
            public int PageIndex;
            public int PageSize;
        }

        private const int minutesToExpire = 5;

        private static Dictionary<object, PagingParameter?> _queryablePagingParameters = new Dictionary<object, PagingParameter?>();
        private static Dictionary<object, DateTime> _queryablePagingTicket = new Dictionary<object, DateTime>();

        /// <summary>
        /// Creates an linq expression to run the query and paginate it in the database returning to the 
        /// application only the requested page with the specified maximum amount of records.
        /// </summary>
        /// <typeparam name="TSource">The System.Type of the target table.</typeparam>
        /// <param name="source">The provider to evaluate queries against a specific data source.</param>
        /// <param name="pageIndex">The zero-based index of the result page.</param>
        /// <param name="pageSize">The maximum amount of records to be returned.</param>
        public static IQueryable<TSource> GetPage<TSource>(this IQueryable<TSource> source, int pageIndex, int pageSize)
        {
            if (!_queryablePagingParameters.ContainsKey(source))
            {
                _queryablePagingParameters.Add(source, new PagingParameter() { PageIndex = pageIndex, PageSize = pageSize });
                _queryablePagingTicket.Add(source, DateTime.Now);
            }

            new Action(AnalysisExpiredPagingParameters).BeginInvoke(null, null);

            var pageIndexExpression = Expression.Constant(pageIndex, typeof(int));
            var pageSizeExpression = Expression.Constant(pageSize, typeof(int));

            return source.Provider.CreateQuery<TSource>(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression, pageIndexExpression, pageSizeExpression }));
        }

        internal static void AnalysisExpiredPagingParameters()
        {
            var sourceKeys = _queryablePagingTicket.Keys.ToList();
            for (int i = 0; i < sourceKeys.Count; i++)
            {
                if ((DateTime.Now - _queryablePagingTicket[sourceKeys[i]]).TotalMinutes > minutesToExpire)
                {
                    _queryablePagingParameters.Remove(sourceKeys[i]);
                    _queryablePagingTicket.Remove(sourceKeys[i--]);
                }
            }
        }

        /// <summary>
        /// Converts the query result to a LeanEngine.Collections.PagedList.
        /// </summary>
        /// <typeparam name="TSource">The System.Type of the target table.</typeparam>
        /// <param name="source">The provider to evaluate queries against a specific data source.</param>
        public static PagedList<TSource> ToPagedList<TSource>(this IQueryable<TSource> source)
            where TSource : class
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            var pagingParameter = _queryablePagingParameters.First().Value;

            try
            {
                return new PagedList<TSource>()
                    {
                        PageIndex = pagingParameter.HasValue ? pagingParameter.Value.PageIndex : -1,
                        PageSize = pagingParameter.HasValue ? pagingParameter.Value.PageSize : -1,
                        TotalItems = source.Count(),
                        Items = new List<TSource>(source)
                    };
            }
            finally
            {
                _queryablePagingParameters.Remove(source);
                _queryablePagingTicket.Remove(source);
            }
        }
    }
}
